import { hmacDigest } from './utils/hmac-digest';
import { timingSafeEqual } from './utils/timing-safe-equal';

class HOTP {
  secret: string;
  counter: number;
  digits: number;
  window: number;
  /**
   * 默认配置.
   * @type {{
   *   secret: string,
   *   counter: number
   *   digits: number
   *   window: number
   * }}
   */
  static get defaults() {
    return {
      secret: '123',
      counter: 1,
      digits: 6,
      window: 1,
    };
  }

  /**
   * 创建一个HOTP对象
   * @param {Secret|string} [config.secret=Secret] 密钥.
   * @param {number} [config.counter=0] 计步器.
   * @param {number} [config.digits=6] 生成的token长度.
   * @param {number} [config.window=1] 允许时间差.
   */
  constructor({ secret = HOTP.defaults.secret, counter = HOTP.defaults.counter, digits = HOTP.defaults.digits, window = HOTP.defaults.window } = {}) {
    /**
     * 密钥.
     * @type {string}
     */
    this.secret = secret;
    /**
     * 计步器.
     * @type {number}
     */
    this.counter = counter;
    /**
     * 生成的token长度.
     * @type {number}
     */
    this.digits = digits;
    /**
     * 要测试的计数器值.
     * @type {number}
     */
    this.window = window;
  }

  /**
   * 生成一个 HOTP token.
   * @param {secret|string} [config.secret=secret] 密钥.
   * @param {number} [config.counter=0] 计步器.
   * @returns {string} Token.
   */
  static generate({ counter = HOTP.defaults.counter, secret = HOTP.defaults.secret, digits = HOTP.defaults.digits }): string {
    return hmacDigest(counter, secret, digits);
  }

  /**
   * 生成一个 HOTP token.
   * @param {secret|string} [config.secret=secret] 密钥.
   * @param {number} [config.counter=0] 计步器.
   * @returns {string} Token.
   */
  generate(): string {
    return HOTP.generate({ counter: this.counter, secret: this.secret, digits: this.digits });
  }

  /**
   * 验证 HOTP token.
   * @param {string} config.token Token.
   * @param {secret|string} [config.secret=secret] 密钥.
   * @param {number} [config.counter=0] 计步器.
   * @param {number} [config.digits=6] 生成的token长度.
   * @returns {string} Token.
   */
  static validate({
    token = '',
    counter = HOTP.defaults.counter,
    secret = HOTP.defaults.secret,
    digits = HOTP.defaults.digits,
    window = HOTP.defaults.window,
  }): boolean {
    if (token.length !== digits) {
      return false;
    }

    let delta = 1;

    for (let i = counter - window; i <= counter + window; ++i) {
      const generatedToken = HOTP.generate({ counter: i, secret, digits });
      if (timingSafeEqual(token, generatedToken)) {
        delta = i - counter;
      }
    }
    return delta === 0;
  }

  validate(token: string): boolean {
    return HOTP.validate({ token, counter: this.counter, secret: this.secret, digits: this.digits, window: this.window });
  }
}

export { HOTP };
